/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2012/11/20
* File: EnnSceneNode.cpp
**/

#include "EnnSceneNode.h"

namespace Enn
{
	SceneNode::SceneNode(const String& name, SceneManager* creator)
		: _name(name)
		, _creator(creator)
		, _parent(0)
		, _listener(0)
		, _self_transform_cache_dirty(true)
		, _child_transform_cache_dirty(true)
		, _self_world_matrix_dirty(true)
		, _parent_transform_cache_dirty(true)
	{

	}

	SceneNode::~SceneNode()
	{
		if (_listener)
		{
			_listener->SceneNodeDestroyed(this);
		}

		removeAllChild();

		if (_parent)
		{
			_parent->removeChild(this);
		}
	}

	SceneNode* SceneNode::getParent() const
	{
		return _parent;
	}

	void SceneNode::setParent(SceneNode* parent)
	{
		_parent = parent;
	}

	const quat& SceneNode::getOrientation() const
	{
		return _orientation;
	}

	void SceneNode::setOrientation( const quat& q )
	{
		_orientation = q;
		_orientation.normalise();
		transformCacheDirty();
	}

	const vec3& SceneNode::getPosition() const
	{
		return _position;
	}

	void SceneNode::setPosition(const vec3& pos)
	{
		ENN_ASSERT(!pos.isNaN());
		_position = pos;
		transformCacheDirty();
	}

	const vec3& SceneNode::getScale() const
	{
		return _scale;
	}

	void SceneNode::setScale(const vec3& scale)
	{
		ENN_ASSERT(!scale.isNaN());
		_scale = scale;
		transformCacheDirty();
	}

	void SceneNode::scale(const vec3& scale)
	{
		_scale *= scale;
		transformCacheDirty();
	}

	void SceneNode::translate(const vec3& trans, TransformSpace relativeTo)
	{
		switch (relativeTo)
		{
		case TS_PARENT:
			{
				_position += trans;
				break;
			}

		case TS_LOCAL:
			{
				_position += _orientation * trans;
				break;
			}

		case TS_WORLD:
			{
				if (_parent)
				{
					_position += (_parent->getWorldOrientation().Inverse() * trans) 
						/ _parent->getWorldScale(); 
				}
				else
				{
					_position += trans;
				}

				break;
			}
		}

		transformCacheDirty();
	}

	void SceneNode::roll(const Radian& angle, TransformSpace relativeTo)
	{
		rotate(vec3::UNIT_Z, angle, relativeTo);
	}

	void SceneNode::pitch(const Radian& angle, TransformSpace relativeTo)
	{
		rotate(vec3::UNIT_X, angle, relativeTo);
	}

	void SceneNode::yaw(const Radian& angle, TransformSpace relativeTo)
	{
		rotate(vec3::UNIT_Y, angle, relativeTo);
	}

	/** Rotate the node around an arbitrary axis.
    */
    void SceneNode::rotate(const vec3& axis, const Radian& angle, TransformSpace relativeTo)
	{
		quat q;
		q.FromAngleAxis(angle,axis);
		rotate(q, relativeTo);
	}

	/** Rotate the node around an aritrary axis using a Quarternion.
    */
    void SceneNode::rotate(const quat& q, TransformSpace relativeTo)
	{
		quat qnorm = q;
		qnorm.normalise();

		switch (relativeTo)
		{
		case TS_LOCAL:
			{
				_orientation = _orientation * qnorm;
				break;
			}
			
		case TS_PARENT:
			{
				_orientation = qnorm * _orientation;
				break;
			}
			
		case TS_WORLD:
			{
				_orientation = _orientation * getWorldOrientation().Inverse()
					* qnorm * _orientation * getWorldOrientation();
				break;
			}
			
		default:
			break;
		}
	}

	SceneNode* SceneNode::createChild(
		const String& name,
		const vec3& trans,
		const quat& rot)
	{
		SceneNode* node = createChildImpl(name);

		node->translate(trans);
		node->rotate(rot);

		addChild(node);

		return node;
	}

	SceneNode* SceneNode::createChildImpl(const String& name)
	{
		ENN_ASSERT(_creator);
		return 0;
	}

	void SceneNode::addChild(SceneNode* child)
	{
		ENN_ASSERT(!child->getParent());

		_child_list.insert(ChildMap::value_type(child->getName(), child));
		child->setParent(this);
	}

	uint16 SceneNode::numChildren() const
	{
		return _child_list.size();
	}

	SceneNode* SceneNode::getChild(const String& name) const
	{
		ChildMap::const_iterator iter = _child_list.find(name);
		if (iter == _child_list.end())
		{
			return 0;
		}

		return iter->second;
	}

	SceneNode* SceneNode::removeChild(const String& name)
	{
		ChildMap::iterator iter = _child_list.find(name);
		if (iter != _child_list.end())
		{
			SceneNode* node = iter->second;

			_child_list.erase(iter);
			node->setParent(0);

			return node;
		}

		return 0;
	}

	SceneNode* SceneNode::removeChild(SceneNode* scene_node)
	{
		if (scene_node)
		{
			ChildMap::iterator iter = _child_list.find(scene_node->getName());
			if (iter != _child_list.end())
			{
				SceneNode* node = iter->second;

				_child_list.erase(iter);
				node->setParent(0);
			}
		}
		
		return 0;
	}

	void SceneNode::removeAllChild()
	{
		ChildMap::iterator iter = _child_list.begin();
		for (; iter != _child_list.end(); ++iter)
		{
			SceneNode* node = iter->second;
			node->setParent(0);
		}

		_child_list.clear();
	}

	void SceneNode::updateFromParent()
	{
		updateFromParentImpl();

		if (_listener)
		{
			_listener->SceneNodeUpdated(this);
		}
	}

	void SceneNode::updateFromParentImpl()
	{
		if (_parent)
		{
			const quat& parentQuat = _parent->getWorldOrientation();
			_world_orientation = parentQuat * _orientation;

			const vec3& parentScale = _parent->getWorldScale();
			_world_scale = parentScale * _world_scale;

			_world_position = parentQuat * (parentScale * _position);
			_world_position += _parent->getWorldPosition();
		}
		else
		{
			_world_orientation = _orientation;
			_world_scale = _scale;
			_world_position = _position;
		}

		setSelfTransformCacheDirty(false);
		setParentTransformCacheUpdated(false);

		_self_world_matrix_dirty = true;
	}

	const quat& SceneNode::getWorldOrientation()
	{
		if (isSelfTransformCacheDirty() || isParentTransformCacheDirty())
		{
			updateFromParent();
		}

		return _world_orientation;
	}

	const vec3& SceneNode::getWorldScale()
	{
		if (isSelfTransformCacheDirty() || isParentTransformCacheDirty())
		{
			updateFromParent();
		}

		return _world_scale;
	}

	const vec3& SceneNode::getWorldPosition()
	{
		if (isSelfTransformCacheDirty() || isParentTransformCacheDirty())
		{
			updateFromParent();
		}

		return _world_position;
	}

	const mat4& SceneNode::getWorldTransform()
	{
		if (_self_world_matrix_dirty)
		{
			_world_matrix.makeTransform(getWorldPosition(),
				getWorldScale(),
				getWorldOrientation());

			_self_world_matrix_dirty = false;
		}

		return _world_matrix;
	}

	void SceneNode::setListener(Listener* listener)
	{
		_listener = listener;
	}

	SceneNode::Listener* SceneNode::getListener() const
	{
		return _listener;
	}

	void SceneNode::setParentTransformCacheUpdated(bool dirty)
	{
		_parent_transform_cache_dirty = dirty;
	}

	void SceneNode::updateTransform()
	{
		if (isParentTransformCacheDirty() || isSelfTransformCacheDirty())
		{
			updateFromParent();
		}

		if (isChildTransformCacheDirty())
		{
			ChildMap::iterator iter = _child_list.begin();
			for (; iter != _child_list.end(); ++iter)
			{
				SceneNode* child = iter->second;

				child->setParentTransformCacheUpdated(true);
			}

			setChildTransformCacheDirty(false);
		}
	}

	void SceneNode::setSelfTransformCacheDirty(bool dirty)
	{
		_self_transform_cache_dirty = dirty;
	}

	void SceneNode::setChildTransformCacheDirty(bool dirty)
	{
		_child_transform_cache_dirty = dirty;
	}

	bool SceneNode::isSelfTransformCacheDirty() const
	{
		return _self_transform_cache_dirty;
	}

	bool SceneNode::isChildTransformCacheDirty() const
	{
		return _child_transform_cache_dirty;
	}

	bool SceneNode::isParentTransformCacheDirty() const
	{
		return _parent_transform_cache_dirty;
	}

	void SceneNode::transformCacheDirty(bool dirty)
	{
		setSelfTransformCacheDirty(dirty);
		setChildTransformCacheDirty(dirty);
	}
}